﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class FlowData : IFlowData
{
    VisualizationData IFlowData.GetFlowData(string startDate, string endDate, string timezoneOffset)
    {
        // initialise
        List<PortData> portData = new List<PortData>();
        List<OrchStepData> orchStepData = new List<OrchStepData>();
        List<LinkData> linkData = new List<LinkData>();
        string connectionString = ConfigurationManager.ConnectionStrings["BizTalkTrackingDB"].ConnectionString;

        // execute the stored procedure
        try
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                conn.Open();
                // Create an EntityCommand.
                using (SqlCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = "usp_GetVisualizationFlowData";
                    cmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter paramStart = new SqlParameter();
                    paramStart.Value = Convert.ToDateTime(startDate);
                    paramStart.ParameterName = "StartDate";
                    cmd.Parameters.Add(paramStart);
                    SqlParameter paramEnd = new SqlParameter();
                    paramEnd.Value = Convert.ToDateTime(endDate);
                    paramEnd.ParameterName = "EndDate";
                    cmd.Parameters.Add(paramEnd);
                    SqlParameter paramOffset = new SqlParameter();
                    paramOffset.Value = Convert.ToInt32(timezoneOffset);
                    paramOffset.ParameterName = "TimezoneOffset";
                    cmd.Parameters.Add(paramOffset);
                    cmd.CommandTimeout = 120;

                    // Execute the command.
                    using (SqlDataReader rdr =
                        cmd.ExecuteReader())
                    {
                        // stored proc will return 3 resultsets: port Info, orchestration step info, linked event info
                        // First, the port info
                        while (rdr.Read())
                        {
                            portData.Add(new PortData()
                            {
                                AvgMilliSeconds = Convert.ToInt32(rdr["AvgMilliseconds"]),
                                ErrorMessageCount = Convert.ToInt32(rdr["ErrorMessageCount"]),
                                ShapeID = rdr["PortShapeID"].ToString(),
                                PortName = rdr["PortName"].ToString(),
                                PortType = rdr["PortType"].ToString(),
                                ReceiveMessageCount = Convert.ToInt32(rdr["ReceiveMessageCount"])
                            });

                            // if there are errors on the port, add a related error shape as well and a link to the error
                            if (Convert.ToInt32(rdr["ErrorMessageCount"]) > 0)
                            {
                                portData.Add(new PortData()
                                {
                                    AvgMilliSeconds = 0,
                                    ErrorMessageCount = Convert.ToInt32(rdr["ErrorMessageCount"]),
                                    ShapeID = rdr["PortShapeID"].ToString() + "-Error",
                                    PortName = rdr["PortName"].ToString() + " - Error",
                                    PortType = "Error",
                                    ReceiveMessageCount = 0
                                });

                                linkData.Add(new LinkData()
                                {
                                    AvgMilliSeconds = 0,
                                    FromShapeID = rdr["PortShapeID"].ToString(),
                                    FromType = rdr["PortType"].ToString(),
                                    LinkType = "Error",
                                    MessageCount = Convert.ToInt32(rdr["ErrorMessageCount"]),
                                    ToShapeID = rdr["PortShapeID"].ToString() + "-Error",
                                    ToType = "Error"
                                });
                            }
                        }

                        // next the orchestration step info
                        rdr.NextResult();
                        while (rdr.Read())
                        {
                            orchStepData.Add(new OrchStepData()
                            {
                                ErrorCount = Convert.ToInt32(rdr["ErrorCount"]),
                                LineNumber = Convert.ToInt32(rdr["LineNumber"]),
                                MessageCount = Convert.ToInt32(rdr["MessageCount"]),
                                OrchID = rdr["OrchID"].ToString(),
                                OrchName = rdr["OrchestrationName"].ToString(),
                                StepID = rdr["OrchStepID"].ToString(),
                                StepName = rdr["StepName"].ToString()
                            });

                            if (Convert.ToInt32(rdr["ErrorCount"]) > 0)
                            {
                                portData.Add(new PortData()
                                {
                                    AvgMilliSeconds = 0,
                                    ErrorMessageCount = Convert.ToInt32(rdr["ErrorCount"]),
                                    ShapeID = rdr["OrchStepID"].ToString() + "-Error",
                                    PortName = rdr["StepName"].ToString() + " - Error",
                                    PortType = "Error",
                                    ReceiveMessageCount = 0
                                });

                                linkData.Add(new LinkData()
                                {
                                    AvgMilliSeconds = 0,
                                    FromShapeID = rdr["OrchStepID"].ToString(),
                                    FromType = "Orc",
                                    LinkType = "Error",
                                    MessageCount = Convert.ToInt32(rdr["ErrorCount"]),
                                    ToShapeID = rdr["OrchStepID"].ToString() + "-Error",
                                    ToType = "Error"
                                });
                            }
                        }

                        // next event link info
                        rdr.NextResult();
                        while (rdr.Read())
                        {
                            linkData.Add(new LinkData()
                                {
                                    AvgMilliSeconds = Convert.ToInt32(rdr["AvgMilliSeconds"]),
                                    FromShapeID = rdr["FromShapeID"].ToString(),
                                    LinkType = rdr["LinkType"].ToString(),
                                    MessageCount = Convert.ToInt32(rdr["MessageCount"]),
                                    ToType = rdr["ToType"].ToString(),
                                    FromType = rdr["FromType"].ToString(),
                                    ToShapeID = rdr["ToShapeID"].ToString()
                                });
                        }
                    }
                }
                conn.Close();
            }
        }
        catch (Exception ex)
        {
            throw new WebFaultException<string>(string.Format(
                "An error occurred while querying the database for the visualization data.  Error message: {0}",
                ex.Message), HttpStatusCode.InternalServerError);
        }

        // sort the data in a way that makes the drawing of the shapes and the links easier
        List<DisplayOrder> sortOrder = sortVisualizationData(ref portData, ref orchStepData, ref linkData);

        // put the data together for return
        VisualizationData sortedData = new VisualizationData()
        {
            links = linkData.ToArray(),
            orchSteps = orchStepData.ToArray(),
            ports = portData.ToArray(),
            displayOrder = sortOrder.ToArray()
        };

        // return the results
        return sortedData;
    }

    /// <summary>
    /// Sort the provided data in a manner that will make rendering the links easier.
    /// </summary>
    /// <returns>Visualization data sorted for display</returns>
    private List<DisplayOrder> sortVisualizationData(ref List<PortData> portData, ref List<OrchStepData> orchStepData, ref List<LinkData> linkData)
    {
        /* Basic logic is the following:
         * - Find all messaging links that do not have any references to orchestrations
         * - Sort these alphabetically by receive port and send port combination, with errors first in the list,
         *          then add these to the ordered list
         * - Next, find the unique orchestrations and loop through these by name and line number
         * - For each step in the orchestration:
         *      - if receiving, find the associated receive port, then find its errors and send ports, sort by link type then send port name,
         *          then add these to the ordered list
         *      - the orchestration step should be added next,
         *          so add the current step to the ordered list
         *      - if sending, find any related errors and send ports, sort by send port name, with errors first in the list,
         *          then add these to the ordered list
         * - For pipeline records, use the data to update the time and message counts for the appropriate rows in the ordered list
         * - For Two-Way Send ports
         *          add link info
         * - As we may have additional steps that are not addressed by the above, but we may need them,
         *          add these to the ordered list
         */

        // initalise
        int sortOrder = 1;
        List<DisplayOrder> sortedDisplay = new List<DisplayOrder>();

        // start with "standalone" receive ports
        #region Receive ports that have no orchestration references are shown first
        // receive ports not linked to any orchestrations, sorted by port name
        var orchReceivePorts = from receivePort in portData
                               join orchReceive in linkData on receivePort.ShapeID equals orchReceive.FromShapeID
                               where orchReceive.FromType == "Rcv" && orchReceive.ToType == "Orc"
                               select receivePort;
        var allReceivePorts = from receivePort in portData
                              where receivePort.PortType == "Rcv"
                              select receivePort;
        var messagingOnlyReceivePorts = allReceivePorts.Except(orchReceivePorts).OrderBy(x => x.PortName);

        // cycle through each receive port
        foreach (var receivePort in messagingOnlyReceivePorts)
        {
            // need related send ports, ordered by send port name
            IEnumerable<LinkData> relatedSendLinks = from item in linkData
                                                     join send in portData on item.ToShapeID equals send.ShapeID
                                                     where item.FromShapeID == receivePort.ShapeID && item.ToType == "Snd"
                                                     orderby send.PortName
                                                     select item;

            // add this receive port and related ones
            addReceiveAndRelatedPorts(receivePort, relatedSendLinks, ref sortedDisplay, ref sortOrder);
        }
        #endregion

        #region Orchestration steps & related receive and send ports
        // steps within an orchestration, sorted by orchestrtion name and line number
        var orchSteps = from orch in orchStepData
                        orderby orch.OrchName, orch.OrchID, orch.LineNumber
                        select orch;

        // loop over each orchestration step
        foreach (var orchStep in orchSteps)
        {
            // receive ports linked to the orchestration
            var orchestrationReceivePorts = from port in portData
                                            join link in linkData on port.ShapeID equals link.FromShapeID
                                            where link.FromType == "Rcv" && link.ToShapeID == orchStep.StepID
                                            orderby port.PortName
                                            select port;

            // add receive ports linked to the current orchestration step, and send ports linked to that receive port
            foreach (var receivePort in orchestrationReceivePorts)
            {
                // need related send ports, ordered by send port name
                IEnumerable<LinkData> relatedSendLinks = from link in linkData
                                                         join send in portData on link.ToShapeID equals send.ShapeID
                                                         where link.FromShapeID == receivePort.ShapeID && link.ToType == "Snd"
                                                         orderby send.PortName
                                                         select link;

                // add this receive port and related ones
                addReceiveAndRelatedPorts(receivePort, relatedSendLinks, ref sortedDisplay, ref sortOrder);
            }

            // add the orchestration step to the sorted display list
            sortedDisplay.Add(new DisplayOrder()
            {
                ID = orchStep.StepID,
                OrderIndex = sortOrder,
                Type = "Orc"
            });

            // increment the sort order
            sortOrder++;

            // add send ports linked to the current orchestration step
            IEnumerable<LinkData> relatedOrchSendLinks = from link in linkData
                                                         join send in portData on link.ToShapeID equals send.ShapeID
                                                         where link.FromShapeID == orchStep.StepID && link.ToType == "Snd"
                                                         orderby send.PortName
                                                         select link;

            // add the related send ports
            foreach (var sendPort in relatedOrchSendLinks)
            {
                // add the send to the sorted display list
                sortedDisplay.Add(new DisplayOrder()
                {
                    ID = sendPort.ToShapeID,
                    OrderIndex = sortOrder,
                    Type = "Snd"
                });

                // increment the sort order
                sortOrder++;
            }
        }
        #endregion

        // return the sorted list
        return sortedDisplay;
    }

    private void addReceiveAndRelatedPorts(PortData receivePort, IEnumerable<LinkData> relatedSendLinks, ref List<DisplayOrder> sortedDisplay, ref int sortOrder)
    {
        // add the receive port to the sorted display list
        sortedDisplay.Add(new DisplayOrder()
        {
            ID = receivePort.ShapeID,
            OrderIndex = sortOrder,
            Type = receivePort.PortType
        });

        // increment the sort order
        sortOrder++;

        // add each related send port
        foreach (var sendLink in relatedSendLinks)
        {
            sortedDisplay.Add(new DisplayOrder()
            {
                ID = sendLink.ToShapeID,
                OrderIndex = sortOrder,
                Type = sendLink.ToType
            });
            sortOrder++;
        }

    }
}
